function Model(webRoot, entityId, colId, orderEntity, orderColumn, order, limit, page, cantRows, url, rowUrl, saveUrl){

    this.webRoot     = webRoot;
    this.columns     = new Array();
    this.filter      = new Array();
    this.userFilter  = new Array();
    this.orderColumn = {entityName: orderEntity, column: orderColumn};
    this.order       = order;
    this.limit       = limit;
    this.page        = page;
    this.cantRows    = cantRows;
    this.url         = url;
    this.fullUrl     = "";
    this.rowUrl      = rowUrl;
    this.saveUrl     = saveUrl;
    this.idColumn    = {entityName: entityId, column: colId, value: undefined};
    this.operation   = "";
    this.onLoadStartListeners       = new Array();
    this.onLoadListeners            = new Array();
    this.onCantPagesListeners       = new Array();
    this.onIdChangedListeners       = new Array();
    this.onSaveListeners            = new Array();
    this.onValidationErrorListeners = new Array();
    this.onRowLoadStartListeners    = new Array();
    this.onRowLoadListeners         = new Array();
    this.runningUploads         = 0;
    
    this.valid                  = true;
    this.errorMsg               = "";
    this.errorColumn            = new Object();

    this.addColumn = function(entityName, column, type, minLength, optional){
        for(var i=0; i<this.columns.length; i++){
            var currCol = this.columns[i];
            if(currCol.entityName == entityName && currCol.column == column){
                return false;
            }
        }
        var col = {entityName: entityName, column: column, type:type,
                   minLength: minLength, optional:optional, value: "",
                   onValueChangedListeners: new Array()};
        this.columns[this.columns.length] = col;
        return true;
    };

    this.setColumnValue = function(entityName, column, value){
        var currCol = this.getColumn(entityName, column);
        if(currCol != undefined){
            currCol.value = value;
            for(var j=0; j<currCol.onValueChangedListeners.length; j++){
                if(currCol.onValueChangedListeners[j] != undefined){
                        currCol.onValueChangedListeners[j].callback(currCol, currCol.onValueChangedListeners[j].context);
                }
            }
        }
    }

    this.clearColumns = function(){
        this.columns = new Array();
    }

    this.getColumn = function(entityName, column){
        for(var i=0; i<this.columns.length; i++){
            var currCol = this.columns[i];
            if(currCol.entityName == entityName && currCol.column == column){
                return currCol;
            }
        }

        return undefined;
    }

    this.getColumnValue = function(entityName, column){
        var col = this.getColumn(entityName, column);

        if(col != undefined){
            return col.value;
        }

        return '';
    }

    this.clearFilter = function(){
        this.filter = new Array();
    }

    this.setFilter = function(entityName, column, value){
        for(var i=0; i<this.columns.length; i++){
            if(column == this.columns[i].column &&
               entityName == this.columns[i].entityName){
                this.filter[i] = value;
                return true;
            }
        }
        return false;
    };

    this.clearUserFilter = function(){
        this.userFilter = new Array();
    }

    this.setUserFilter = function(entityName, column, value){
        for(var i=0; i<this.columns.length; i++){
            if(column == this.columns[i].column &&
               entityName == this.columns[i].entityName){
               if(this.columns[i].type == "date"){
                   value = unformatDate(value);
               }
                this.userFilter[i] = value;
                return true;
            }
        }
        return false;
    };

    this.setOrder = function(entityName, column, ascDesc){
        for(var i=0; i<this.columns.length; i++){
            if(column == this.columns[i].column &&
               entityName == this.columns[i].entityName){
                this.orderColumn = {entityName: entityName, column: column};
                this.order = ascDesc;
                this.setFullUrl();
                return true;
            }
        }
        return false;
    };

    this.first = function(){
        this.page = 0;
    };

    this.prev = function(){
        if(this.page > 0){
            this.page = this.page - 1;
        }
    };

    this.next = function(){
        if(this.page < this.cantPages() - 1){
            this.page++;
        }
    };

    this.last = function(){
        this.page = this.cantPages() - 1;
    };

    this.setLimit = function(limit){

        if(Math.ceil(this.cantRows / limit) != this.cantPages()){
            this.limit = limit;
            this.onCantPagesChanged(this);
        }else{
            this.limit = limit;
        }
        this.setFullUrl();
    };

    this.setPage = function(page){
        if(page >= 0 && page < this.cantPages()){
            this.page = page;
            this.setFullUrl();
            return true;
        }
        return false;
    }

    this.setOrderColumn = function(entityName, column){
        this.orderColumn.entityName = entityName;
        this.orderColumn.column = column;
        this.setFullUrl();
    }

    this.load = function(context){
        var _this = this;
        if(context != undefined){
            _this = context;
        }
                
        if(this.onLoadListeners.length > 1){
            $.post(_this.getFullUrl(),_this.getFilterParameters(), function(data){_this.onLoad(data, _this);}, "json");
            for(var j=0; j<_this.onLoadStartListeners.length; j++){
                if(_this.onLoadStartListeners[j] != undefined){
                    _this.onLoadStartListeners[j].callback(this.onLoadStartListeners[j].context);
                }
            }
        }else if(this.onLoadStartListeners.length > 1){
            for(var j=0; j<_this.onLoadStartListeners.length; j++){
                if(_this.onLoadStartListeners[j] != undefined){
                    _this.onLoadStartListeners[j].callback(this.onLoadStartListeners[j].context);
                }
            }
        }
    };

    this.setFullUrl = function(){
        var rnd = Math.random() * 100;
        this.fullUrl = this.webRoot+this.url+"/"+this.page+"/"+this.limit+"/"+this.orderColumn.entityName+"->"+this.orderColumn.column+"/"+this.order+"/"+rnd;
    }

    this.getFullUrl = function(){
        //var rnd = Math.random() * 100;
        //return this.webRoot+this.url+"/"+this.page+"/"+this.limit+"/"+this.orderColumn.entityName+"->"+this.orderColumn.column+"/"+this.order+"/"+rnd;
        if(this.fullUrl == ""){
            this.setFullUrl();
        }
        return this.fullUrl;
    }

    this.getFilterParameters = function(){
        var parameters = new Object();

        for(var i=0; i<this.columns.length; i++){
            if(this.userFilter[i] != undefined){
                parameters[this.columns[i].entityName+"->"+this.columns[i].column] = this.userFilter[i];
            }
        }

        for(var i=0; i<this.columns.length; i++){
            if(this.filter[i] != undefined){
                parameters[this.columns[i].entityName+"->"+this.columns[i].column] = this.filter[i];
            }
        }

        return parameters;
    }

    this.onLoad = function (data, _this){
        if(Math.ceil(data.pageInfo.total / _this.limit) != _this.cantPages()){
            _this.cantRows = data.pageInfo.total;
            _this.onCantPagesChanged(_this);
        }else{
            _this.cantRows = data.pageInfo.total;
        }
        _this.onTableLoad(data, _this);
    }

    this.onTableLoad = function(data, _this){
        for(var i=0; i<this.onLoadListeners.length; i++){
            this.onLoadListeners[i].callback(data, _this, this.onLoadListeners[i].context);
        }
    };
    
    this.cantPages = function(){
        return Math.ceil(this.cantRows / this.limit);
    };

    this.onCantPagesChanged = function(_this){
        for(var i=0; i<this.onCantPagesListeners.length; i++){
            this.onCantPagesListeners[i].callback(_this, this.onCantPagesListeners[i].context);
        }
    };
    
    this.setEntityId = function(id){
        if(id != this.idColumn.value){
            this.idColumn.value = id;
            for(var i=0; i<this.onIdChangedListeners.length; i++){
                if(this.onIdChangedListeners[i] != undefined){
                    this.onIdChangedListeners[i].callback(id, this.onIdChangedListeners[i].context);
                }
            }
        }
    };
        
    this.loadRow = function(){
        var _this = this;
        if(this.idColumn.value == undefined){
            alert("Debe Seleccionar un registro");
            return false;
        }

        for(var i=0; i<this.onRowLoadStartListeners.length; i++){
            if(this.onRowLoadStartListeners[i] != undefined){
                this.onRowLoadStartListeners[i].callback(this.onRowLoadStartListeners[i].context);
            }
        }

        $.getJSON(this.webRoot+rowUrl+"/"+this.idColumn.value+"/"+(Math.random() * 100), {}, function(data){_this.onRowLoad(data, _this);});
        return true;
    };

    this.onRowLoad = function(data, context){
        for(var j=0; j<this.columns.length; j++){
            var col = this.columns[j];
            this.setColumnValue(col.entityName, col.column, data.data[0][col.entityName][col.column]);
        }
        for(var i=0; i<this.onRowLoadListeners.length; i++){
            if(this.onRowLoadListeners[i] != undefined){
                this.onRowLoadListeners[i].callback(data, context, this.onRowLoadListeners[i].context);
            }
        }
    };

    this.setOperation = function(op){
        if(op != "add" && this.idColumn.value == undefined){
            this.valid = false;
            this.errorMsg = "Es necesario seleccionar un registro";
            this.onValidationError();
            this.errorColumn = undefined;
            return false;
        }
        this.operation = op;

        return true;
    };

    this.save = function (){
        return this.send(this.operation, this.idColumn.value);
    };

    this.send = function(op, id){
        if(!this.validate()){
            return false;
        }

        var isMultipart = false;
        var fileColumn  = new Array();
        var parameters  = new Object();

        for(var i=0; i<this.columns.length; i++){
            var column = this.columns[i];
            var filterType = column.type;
            var value = column.value;

            if(filterType == "file" || filterType == "image"){
                value = new String(value);
                if(value.length > 0){
                    isMultipart = true;
                    fileColumn[fileColumn.length] = column;
                }
            }
            parameters[column.column] = column.value;
        }

        parameters['op'] = op;
        parameters['id'] = id;

        this.doSend(parameters, isMultipart, fileColumn);

        return true;
    };

    this.doSend = function(parameters, isMultipart, fileColumns){
        if(isMultipart){
            this.ajaxFileUpload(url, fileColumns, parameters);
        }else{
            var _this = this;
            $.post(this.webRoot+this.saveUrl, parameters, function(data){_this.onDataSent(data, _this);}, "json");
        }
    };

    this.ajaxFileUpload = function(columns, parameters){
        for(var i=0; i<columns.length; i++){

            var column = columns[i];
            /*$("#"+column+"_loading").ajaxStart(function(){
                $(this).show();
            });*/
            /*$("#"+column+"_loading").ajaxComplete(function(){
                $(this).hide();
            });*/

            this.runningUploads++;

            $.ajaxFileUpload
            (
                {
                    url:webRoot+'admin_upload',
                    secureuri:false,
                    fileElementId:column,
                    dataType: 'json',
                    success: function (data, status)
                    {
                        this.runningUploads--;
                        if(data.error != '')
                        {
                                alert(data.error);
                        }else
                        {
                            parameters[data.column] = data.msg;
                            if(this.runningUploads == 0){
                                var _this = this;
                                $.post(this.webRoot+this.saveUrl, parameters, function(data){_this.onDataSent(data, _this);}, "json");
                            }
                        }

                    },
                    error: function (data, status, e)
                    {
                        alert(e);
                    }
                }
            );
        }

        return false;
	};

    this.onDataSent = function (data, context){
        for(var i=0; i<context.onSaveListeners.length; i++){
            context.onSaveListeners[i].callback(data, context, context.onSaveListeners[i].context);
        }
    };

    this.validateColumn = function(column){
        if(column.type == "password"){
            if(column.test != "" && column.test != undefined){
                if(column.value != column.test){
                    this.valid = false;
                    this.errorMsg = "Los campos "+column.column+" no coinciden.";
                    this.errorColumn = column;
                    this.onValidationError();
                    return;
                }
                return false;
            }
            if(column.value.length < column.minLength && column.value.length > 0){
                this.valid = false;
                this.errorMsg = "El campo "+column.column+" debe tener al menos "+column.minLength+" caracteres.";
                this.errorColumn = column;
                this.onValidationError();
                return false;
            }
        }

        if(column.optional == false || column.value != ""){
            if(column.value == ""){
                this.valid = false;
                this.errorMsg = "El campo "+column.column+" es requerido.";
                this.errorColumn = column;
                this.onValidationError();
                return false;
            }

            if(column.type == "string" && column.value.length < column.minLength){
                this.valid = false;
                this.errorMsg = "El campo "+column.column+" debe tener al menos "+column.minLength+" caracteres.";
                this.errorColumn = column;
                this.onValidationError();
                return false;
            }

            if(column.type == "date" && column.value == "--"){
                alert(column.value);
                this.valid = false;
                this.errorMsg = "El campo "+column.column+" es requerido.";
                this.errorColumn = column;
                this.onValidationError();
                return false;
            }

            if(column.type == "email"){
                if(column.value.indexOf("@") < 0 || column.value.indexOf(".") < 0){
                    this.valid = false;
                    this.errorMsg = "El campo "+column.column+" debe ser un email válido";
                    this.errorColumn = column;
                    this.onValidationError();
                    return false;
                }
            }

            if(column.type == "number"){
                var ValidChars = "0123456789.";
                var Char;

                if(column.value == null){
                    return true;
                }

                for (var i=0; i<column.value.length; i++){
                   Char = column.value.charAt(i);
                   if (ValidChars.indexOf(Char) == -1){
                      this.valid = false;
                      this.errorMsg = "El campo "+column.column+" debe tener un valor numérico.";
                      this.errorColumn = column;
                      this.onValidationError();
                      return false;
                   }
                }
            }
        }
        return true;
    };
    
    this.validate = function(){

        this.valid = true;

        for(var i=0; i<this.columns.length; i++){
            var column      = this.columns[i];            

            this.validateColumn(column);

            if(!this.valid){
                return false;
            }
            
        }
        

        if(this.valid){
            this.errorMsg = "";
            this.errorColumn = new Object();
        }
        return true;
    }

    this.onValidationError = function (){
        for(var i=0; i<this.onValidationErrorListeners.length; i++){
            this.onValidationErrorListeners[i].callback(this.onValidationErrorListeners[i].context);
        }
    };

    ///// ADD LISTENERS

    this.addOnRowLoadListener = function(func, context){
        this.onRowLoadListeners[this.onRowLoadListeners.length] = {callback: func, context:context};
    };

    this.addLoadListener = function(func, context){
        this.onLoadListeners[this.onLoadListeners.length] = {callback: func, context:context};
    };

    this.addOnRowLoadStartListener = function(func, context){
        this.onRowLoadStartListeners[this.onRowLoadStartListeners.length] = {callback: func, context:context};
    }

    this.addOnLoadStartListener = function(func, context){
        this.onLoadStartListeners[this.onLoadStartListeners.length] = {callback: func, context:context};
    }

    this.addCantPagesListener = function(func, context){
        this.onCantPagesListeners[this.onCantPagesListeners.length] = {callback: func, context:context};
    };

    this.addIdChangedListener = function(func, context){
        this.onIdChangedListeners[this.onIdChangedListeners.length] = {callback: func, context:context};
    };

    this.addOnSaveListener = function(func, context){
        this.onSaveListeners[this.onSaveListeners.length] = {callback: func, context:context};
    };

    this.addOnValidationErrorListener = function(func, context){
        this.onValidationErrorListeners[this.onValidationErrorListeners.length] = {callback: func, context:context};
    };

    this.addOnColumnValueChangedListener = function(entityName, column, func, context){
        var col = this.getColumn(entityName, column);
        
        col.onValueChangedListeners[col.onValueChangedListeners.length] = {callback: func, context: context};
        
    }

    ///// REMOVE LISTENERS

    this.removeOnRowLoadStartListener = function(func, context){
        this.removeListener(this.onRowLoadStartListeners, func, context);
    }

    this.removeOnRowLoadListeners = function(func, context){
        this.removeListener(this.onRowLoadListeners, func, context);
    };

    this.removeOnLoadStartListener = function(func, context){
        this.removeListener(this.onLoadStartListeners, func, context);
    }

    this.removeLoadListener = function(func, context){
        this.removeListener(this.onLoadListeners, func, context);
    };

    this.removeCantPagesListener = function(func, context){
        this.removeListener(this.onCantPagesListeners, func, context);
    };

    this.removeIdChangedListener = function(func, context){
        this.removeListener(this.onIdChangedListeners, func, context);
    };

    this.removeOnSaveListener = function(func, context){
        this.removeListener(this.onSaveListeners, func, context);
    };

    this.removeOnRowLoadListener = function(func, context){        
        this.removeListener(this.onRowLoadListeners, func, context);
    };

    this.removeOnColumnValueChangedListener = function(entityName, column, func, context){
        var col = this.getColumn(entityName, column);
        this.removeListener(col.onValueChangedListeners, func, context);
    }

    this.removeListener = function(arrListener, func, context){
        var index = -1;
        for(var i=0; i<arrListener.length; i++){
            if(arrListener[i] != undefined){
                if(arrListener[i].context == context && arrListener[i].callback == func){
                    index = i;
                }
            }
        }

        if(index >= 0){
            delete arrListener[index];
        }
    }

}